package aliyun

import (
	"context"
	"fmt"
	"log/slog"
	"os"
	"strings"
	"time"

	"github.com/aliyun/alibabacloud-oss-go-sdk-v2/oss"
	"github.com/aliyun/alibabacloud-oss-go-sdk-v2/oss/credentials"
	openapicred "github.com/aliyun/credentials-go/credentials"
)

type StsAccessKey struct {
	AccessKeyId     string `json:"accessKeyId"`
	AccessKeySecret string `json:"accessKeySecret"`
	SecurityToken   string `json:"securityToken"`
	CredentialType  string `json:"credentialType"`
}

type OssSvc struct {
	ossClient *oss.Client

	stsUploadLastTime  *time.Time
	stsUploadAccessKey *StsAccessKey
	stsUploadClient    *oss.Client

	stsDownloadLastTime  *time.Time
	stsDownloadAccessKey *StsAccessKey
	stsDownloadClient    *oss.Client

	RegionId        string `json:"regionId"`
	EndPoint        string `json:"endPoint"`
	AccessKeyId     string `json:"accessKeyId"`
	AccessKeySecret string `json:"accessKeySecret"`
	UploadRoleArn   string `json:"uploadRoleArn"`
	DownloadRoleArn string `json:"downloadRoleArn"`
}

func (svc *OssSvc) GetClient() (*oss.Client, error) {
	if svc.ossClient == nil {
		provider := credentials.NewStaticCredentialsProvider(svc.AccessKeyId, svc.AccessKeySecret)
		cfg := oss.LoadDefaultConfig().WithCredentialsProvider(provider).WithRegion(svc.RegionId)
		svc.ossClient = oss.NewClient(cfg)
	}
	return svc.ossClient, nil
}

func (svc *OssSvc) UploadFile(bucketName string, objectKey string, filePath string) error {
	if _, err := os.Stat(filePath); err != nil {
		slog.Error(err.Error())
		return err
	}

	client, err := svc.GetClient()
	if err != nil {
		slog.Error(err.Error())
		return err
	}

	_, err = client.PutObjectFromFile(context.TODO(), &oss.PutObjectRequest{
		Bucket: oss.Ptr(bucketName),
		Key:    oss.Ptr(objectKey),
	}, filePath)
	if err != nil {
		slog.Error(err.Error())
		return err
	}
	return nil
}

func (svc *OssSvc) DownloadFile(bucketName string, objectKey string, filePath string) error {
	client, err := svc.GetClient()
	if err != nil {
		slog.Error(err.Error())
		return err
	}

	_, err = client.GetObjectToFile(context.TODO(), &oss.GetObjectRequest{
		Bucket: oss.Ptr(bucketName),
		Key:    oss.Ptr(objectKey),
	}, filePath)
	if err != nil {
		slog.Error(err.Error())
		return err
	}
	return nil
}

func (svc *OssSvc) CheckFileExist(bucketName string, objectKey string) (bool, error) {
	client, err := svc.GetClient()
	if err != nil {
		slog.Error(err.Error())
		return false, err
	}

	exist, err := client.IsObjectExist(context.TODO(), bucketName, objectKey)
	if err != nil {
		slog.Error(err.Error())
		return false, err
	}
	return exist, err
}

func (svc *OssSvc) CopyFile(srcBucket string, srcKey string, dstBucket string, dstKey string) error {
	client, err := svc.GetClient()
	if err != nil {
		slog.Error(err.Error())
		return err
	}

	_, err = client.CopyObject(context.TODO(), &oss.CopyObjectRequest{
		Bucket:       oss.Ptr(dstBucket),
		Key:          oss.Ptr(dstKey),
		SourceBucket: oss.Ptr(srcBucket),
		SourceKey:    oss.Ptr(srcKey),
	})
	if err != nil {
		slog.Error(err.Error())
		return err
	}

	return nil
}

func (svc *OssSvc) RenameFile(srcBucket string, srcKey string, dstBucket string, dstKey string) error {
	client, err := svc.GetClient()
	if err != nil {
		slog.Error(err.Error())
		return err
	}

	_, err = client.CopyObject(context.TODO(), &oss.CopyObjectRequest{
		Bucket:       oss.Ptr(dstBucket),
		Key:          oss.Ptr(dstKey),
		SourceBucket: oss.Ptr(srcBucket),
		SourceKey:    oss.Ptr(srcKey),
	})
	if err != nil {
		slog.Error(err.Error())
		return err
	}

	_, err = client.DeleteObject(context.TODO(), &oss.DeleteObjectRequest{
		Bucket: oss.Ptr(srcBucket),
		Key:    oss.Ptr(srcKey),
	})
	if err != nil {
		slog.Error(err.Error())
		return err
	}

	return nil
}

func (svc *OssSvc) DeleteFile(bucketName string, objectKey string) error {
	client, err := svc.GetClient()
	if err != nil {
		slog.Error(err.Error())
		return err
	}

	_, err = client.DeleteObject(context.TODO(), &oss.DeleteObjectRequest{
		Bucket: oss.Ptr(bucketName),
		Key:    oss.Ptr(objectKey),
	})
	if err != nil {
		slog.Error(err.Error())
		return err
	}

	return nil
}
func (svc *OssSvc) DeleteDirectory(bucketName string, directory string) error {
	client, err := svc.GetClient()
	if err != nil {
		slog.Error(err.Error())
		return err
	}

	if !strings.HasSuffix(directory, "/") {
		directory += "/"
	}

	request := &oss.ListObjectsRequest{
		Bucket: oss.Ptr(bucketName),
		Marker: oss.Ptr(""),
		Prefix: oss.Ptr(directory),
	}
	paginator := client.NewListObjectsPaginator(request)

	for paginator.HasNext() {
		page, err := paginator.NextPage(context.TODO())
		if err != nil {
			slog.Error(err.Error())
			return err
		}

		for _, obj := range page.Contents {
			_, err := client.DeleteObject(context.TODO(), &oss.DeleteObjectRequest{
				Bucket: oss.Ptr(bucketName),
				Key:    obj.Key,
			})
			if err != nil {
				slog.Error(err.Error())
				return err
			}
		}
	}

	return nil
}

// GenStsAccessKey roleType: upload/download
func (svc *OssSvc) GenStsAccessKey(roleType string) (*StsAccessKey, error) {
	// 30分钟内，不重复获取
	var roleArn string
	if roleType == "upload" {
		roleArn = svc.UploadRoleArn
		if svc.stsUploadAccessKey != nil &&
			svc.stsUploadLastTime != nil &&
			time.Now().Unix()-svc.stsUploadLastTime.Unix() < 1800 {
			return svc.stsUploadAccessKey, nil
		}
	} else {
		roleArn = svc.DownloadRoleArn
		if svc.stsDownloadAccessKey != nil &&
			svc.stsDownloadLastTime != nil &&
			time.Now().Unix()-svc.stsDownloadLastTime.Unix() < 1800 {
			return svc.stsDownloadAccessKey, nil
		}
	}

	config := new(openapicred.Config).
		// Which type of credential you want
		SetType("ram_role_arn").
		// AccessKeyId of your account
		SetAccessKeyId(svc.AccessKeyId).
		// AccessKeySecret of your account
		SetAccessKeySecret(svc.AccessKeySecret).
		// Format: acs:ram::USER_Id:role/ROLE_NAME
		SetRoleArn(roleArn).
		// Role Session Name
		SetRoleSessionName(roleType).
		// Not required, limit the Valid time of STS Token
		SetRoleSessionExpiration(3600)

	arnCredential, err := openapicred.NewCredential(config)
	if err != nil {
		slog.Error(err.Error())
		return nil, err
	}

	accessKeyId, err := arnCredential.GetAccessKeyId()
	if err != nil {
		slog.Error(err.Error())
		return nil, err
	}
	accessKeySecret, err := arnCredential.GetAccessKeySecret()
	if err != nil {
		slog.Error(err.Error())
		return nil, err
	}
	securityToken, err := arnCredential.GetSecurityToken()
	if err != nil {
		slog.Error(err.Error())
		return nil, err
	}
	credentialType := arnCredential.GetType()
	slog.Debug(fmt.Sprint(accessKeyId, accessKeySecret, securityToken, credentialType))

	var accessKey = StsAccessKey{
		AccessKeyId:     *accessKeyId,
		AccessKeySecret: *accessKeySecret,
		SecurityToken:   *securityToken,
		CredentialType:  *credentialType,
	}
	var now = time.Now()
	if roleType == "upload" {
		svc.stsUploadAccessKey = &accessKey
		svc.stsUploadLastTime = &now
		return svc.stsUploadAccessKey, nil
	} else {
		svc.stsDownloadAccessKey = &accessKey
		svc.stsDownloadLastTime = &now
		return svc.stsDownloadAccessKey, nil
	}
}

func (svc *OssSvc) GetStsDownloadClient() (*oss.Client, error) {
	if svc.stsDownloadClient != nil &&
		svc.stsDownloadLastTime != nil &&
		time.Now().Unix()-svc.stsDownloadLastTime.Unix() < 1800 {
		return svc.stsDownloadClient, nil
	}

	stsAccessKey, err := svc.GenStsAccessKey("download")
	if err != nil {
		slog.Error(err.Error())
		return nil, err
	}

	provider := credentials.CredentialsProviderFunc(func(ctx context.Context) (credentials.Credentials, error) {
		return credentials.Credentials{
			AccessKeyID:     stsAccessKey.AccessKeyId,
			AccessKeySecret: stsAccessKey.AccessKeySecret,
			SecurityToken:   stsAccessKey.SecurityToken,
		}, nil
	})
	cfg := oss.LoadDefaultConfig().WithCredentialsProvider(provider)

	svc.stsDownloadClient = oss.NewClient(cfg)
	return svc.stsDownloadClient, nil
}

func (svc *OssSvc) GenStsDownloadUrl(bucketName string, objectKey string, expiredInSec int64) (string, error) {
	client, err := svc.GetStsDownloadClient()
	if err != nil {
		slog.Error(err.Error())
		return "", nil
	}

	// 生成用于下载的签名URL，并指定签名URL的有效时间
	result, err := client.Presign(context.TODO(), &oss.GetObjectRequest{
		Bucket: oss.Ptr(bucketName),
		Key:    oss.Ptr(objectKey),
	}, func(options *oss.PresignOptions) {
		options.Expires = time.Duration(expiredInSec) * time.Second
	})
	if err != nil {
		slog.Error(err.Error())
		return "", nil
	}

	slog.Debug(fmt.Sprintf("Signed Download Url: %s", result.URL))
	return result.URL, nil
}

func (svc *OssSvc) GetStsUploadClient() (*oss.Client, error) {
	if svc.stsUploadClient != nil &&
		svc.stsUploadLastTime != nil &&
		time.Now().Unix()-svc.stsUploadLastTime.Unix() < 1800 {
		return svc.stsUploadClient, nil
	}

	stsAccessKey, err := svc.GenStsAccessKey("upload")
	if err != nil {
		slog.Error(err.Error())
		return nil, err
	}

	provider := credentials.CredentialsProviderFunc(func(ctx context.Context) (credentials.Credentials, error) {
		return credentials.Credentials{
			AccessKeyID:     stsAccessKey.AccessKeyId,
			AccessKeySecret: stsAccessKey.AccessKeySecret,
			SecurityToken:   stsAccessKey.SecurityToken,
		}, nil
	})
	cfg := oss.LoadDefaultConfig().WithCredentialsProvider(provider)

	svc.stsUploadClient = oss.NewClient(cfg)
	return svc.stsUploadClient, nil
}

func (svc *OssSvc) GenStsUploadUrl(bucketName string, objectKey string, expiredInSec int64) (string, error) {
	client, err := svc.GetStsUploadClient()
	if err != nil {
		slog.Error(err.Error())
		return "", nil
	}

	// 生成用于上传的签名URL，并指定签名URL的有效时间
	result, err := client.Presign(context.TODO(), &oss.PutObjectRequest{
		Bucket: oss.Ptr(bucketName),
		Key:    oss.Ptr(objectKey),
	}, func(options *oss.PresignOptions) {
		options.Expires = time.Duration(expiredInSec) * time.Second
	})
	if err != nil {
		slog.Error(err.Error())
		return "", nil
	}

	slog.Debug(fmt.Sprintf("Signed Upload Url: %s", result.URL))
	return result.URL, nil
}
